package com.takwolf.libgdx.tiled;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.utils.Disposable;
import com.google.gson.Gson;
import com.takwolf.libgdx.core.Viewport;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TiledMap implements Disposable {

    private static final Gson gson = new Gson();
    private static Map<String, Texture> managedTextures = new HashMap<>();
    private static Map<String, Integer> managedReferences = new HashMap<>();

    public static Texture loadTexture(FileHandle fileHandle) {
        String path = fileHandle.path();
        Texture texture = managedTextures.get(path);
        if (texture == null) {
            texture = new Texture(fileHandle);
            managedTextures.put(path, texture);
        }
        Integer reference = managedReferences.get(path);
        if (reference == null) reference = 0;
        reference++;
        managedReferences.put(path, reference);
        return texture;
    }

    public static void disposeTexture(FileHandle fileHandle) {
        String path = fileHandle.path();
        Integer reference = managedReferences.get(path);
        if (reference == null) reference = 0;
        reference--;
        if (reference <= 0) {
            Texture texture = managedTextures.get(path);
            if (texture != null) {
                managedTextures.remove(path);
                texture.dispose();
            }
            managedReferences.put(path, 0);
        } else {
            managedReferences.put(path, reference);
        }
    }

    private Tiled tiled;
    private Map<String, Tileset> tilesets;
    private Map<String, Layer> layers;
    private TextureRegion[] tileRegions;
    private List<FileHandle> textureFileHandles;

    private boolean disposed = false;

    public TiledMap(FileHandle fileHandle) {
        tiled = gson.fromJson(fileHandle.readString(), Tiled.class);

        int tileCount = 0;
        tilesets = new HashMap<>();
        for (Tileset tileset : tiled.getTilesets()) {
            tilesets.put(tileset.getName(), tileset);
            tileCount += tileset.getImageWidth() / tileset.getTileWidth() * tileset.getImageHeight() / tileset.getTileHeight();
        }
        textureFileHandles = new ArrayList<>();
        tileRegions = new TextureRegion[tileCount];
        String parentPath = fileHandle.file().getParent();
        for (Tileset tileset : tiled.getTilesets()) {
            FileHandle textureFileHandle = Gdx.files.getFileHandle(parentPath == null ? tileset.getImage() : parentPath + "/" + tileset.getImage(), fileHandle.type());
            Texture texture = loadTexture(textureFileHandle);
            textureFileHandles.add(textureFileHandle);
            TextureRegion[][] temp = TextureRegion.split(texture, tileset.getTileWidth(), tileset.getTileHeight());
            for (int i = 0; i < temp.length; i++) {
                for (int j = 0; j < temp[i].length; j++) {
                    tileRegions[tileset.getFirstGid() - 1 + i * temp[i].length + j] = temp[i][j];
                }
            }
        }

        layers = new HashMap<>();
        for (Layer layer : tiled.getLayers()) {
            layers.put(layer.getName(), layer);
        }
    }

    @Override
    public void dispose() {
        if (!disposed) {
            disposed = true;
            for (FileHandle fileHandle : textureFileHandles) {
                disposeTexture(fileHandle);
            }
        }
    }

    public void drawLayer(Batch batch, Layer layer, float x, float y, Rectangle area, boolean showInvisible) {
        if (layer.isVisible() || showInvisible) {
            for (int n = 0; n < layer.getData().length; n++) {
                TextureRegion tileRegion = getTileRegion(layer.getData()[n]);
                if (tileRegion != null) {
                    float posX = x + (n % getColumn()) * getTileWidth();
                    float posY = y + layer.getHeight() + (getRow() - n / getColumn()) * getTileHeight() - tileRegion.getRegionHeight();
                    if (area == null || area.overlaps(new Rectangle(posX, posY, tileRegion.getRegionWidth(), tileRegion.getRegionHeight()))) {
                        batch.draw(tileRegion, posX, posY);
                    }
                }
            }
        }
    }

    public void drawLayer(Batch batch, Layer layer, float x, float y, Rectangle area) {
        drawLayer(batch, layer, x, y, area, false);
    }

    public void drawLayer(Batch batch, Layer layer, float x, float y, boolean showInvisible) {
        drawLayer(batch, layer, x, y, null, showInvisible);
    }

    public void drawLayer(Batch batch, Layer layer, float x, float y) {
        drawLayer(batch, layer, x, y, false);
    }

    public void drawLayer(Batch batch, Layer layer, Rectangle area, boolean showInvisible) {
        drawLayer(batch, layer, 0, 0, area, showInvisible);
    }

    public void drawLayer(Batch batch, Layer layer, Rectangle area) {
        drawLayer(batch, layer, area, false);
    }

    public void drawLayer(Batch batch, Layer layer, boolean showInvisible) {
        drawLayer(batch, layer, 0, 0, showInvisible);
    }

    public void drawLayer(Batch batch, Layer layer) {
        drawLayer(batch, layer, false);
    }

    public void drawLayerViewport(Batch batch, Layer layer, Viewport viewport, float x, float y, boolean showInvisible) {
        drawLayer(batch, layer, x, y, viewport.getRectangle(), showInvisible);
    }

    public void drawLayerViewport(Batch batch, Layer layer, Viewport viewport, float x, float y) {
        drawLayerViewport(batch, layer, viewport, x, y, false);
    }

    public void drawLayerViewport(Batch batch, Layer layer, Viewport viewport, boolean showInvisible) {
        drawLayerViewport(batch, layer, viewport, 0, 0, showInvisible);
    }

    public void drawLayerViewport(Batch batch, Layer layer, Viewport viewport) {
        drawLayerViewport(batch, layer, viewport, false);
    }

    public void draw(Batch batch, float x, float y, Rectangle area, boolean showInvisible) {
        for (Layer layer : tiled.getLayers()) {
            drawLayer(batch, layer, x, y, area, showInvisible);
        }
    }

    public void draw(Batch batch, float x, float y, Rectangle area) {
        draw(batch, x, y, area, false);
    }

    public void draw(Batch batch, float x, float y, boolean showInvisible) {
        draw(batch, x, y, null, showInvisible);
    }

    public void draw(Batch batch, float x, float y) {
        draw(batch, x, y, false);
    }

    public void draw(Batch batch, Rectangle area, boolean showInvisible) {
        draw(batch, 0, 0, area, showInvisible);
    }

    public void draw(Batch batch, Rectangle area) {
        draw(batch, area, false);
    }

    public void draw(Batch batch, boolean showInvisible) {
        draw(batch, 0, 0, showInvisible);
    }

    public void draw(Batch batch) {
        draw(batch, false);
    }

    public void drawViewport(Batch batch, Viewport viewport, float x, float y, boolean showInvisible) {
        draw(batch, x, y, viewport.getRectangle(), showInvisible);
    }

    public void drawViewport(Batch batch, Viewport viewport, float x, float y) {
        drawViewport(batch, viewport, x, y, false);
    }

    public void drawViewport(Batch batch, Viewport viewport, boolean showInvisible) {
        drawViewport(batch, viewport, 0, 0, showInvisible);
    }

    public void drawViewport(Batch batch, Viewport viewport) {
        drawViewport(batch, viewport, false);
    }

    public Tiled getTiled() {
        return tiled;
    }

    public int getVersion() {
        return tiled.getVersion();
    }

    public int getColumn() {
        return tiled.getColumn();
    }

    public int getRow() {
        return tiled.getRow();
    }

    public int getTileWidth() {
        return tiled.getTileWidth();
    }

    public int getTileHeight() {
        return tiled.getTileHeight();
    }

    public int getWidth() {
        return tiled.getWidth();
    }

    public int getHeight() {
        return tiled.getHeight();
    }

    public List<Tileset> getTilesets() {
        return tiled.getTilesets();
    }

    public Tileset getTileset(String name) {
        return tilesets.get(name);
    }

    public List<Layer> getLayers() {
        return tiled.getLayers();
    }

    public Layer getLayer(String name) {
        return layers.get(name);
    }

    public TextureRegion[] getTileRegions() {
        return tileRegions;
    }

    public TextureRegion getTileRegion(int gid) {
        if (gid < 1 || gid > tileRegions.length) {
            return null;
        } else {
            return tileRegions[gid - 1];
        }
    }

}
